package com.xuecheng.media.service.impl;

import com.xuecheng.base.exception.XueChengPlusException;
import com.xuecheng.content.model.dto.MultPartFileInfoInit;
import com.xuecheng.media.config.MinioConfig;
import com.xuecheng.media.service.MinIoService;
import io.minio.*;
import io.minio.errors.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.*;

@Slf4j
@Service
public class MinIoServiceImpl implements MinIoService {

    @Autowired
    MinioConfig minioConfig;

    @Autowired
    private MinioClient minioClient;


    @Value("${minio.bucket.files}")
    private String BUCKET_FILES;

    @Value("${minio.bucket.videofiles}")
    private String BUCKET_VIDEOS;

    @Value("${minio.bucket.chunkSizeMB}")
    private  int CHUNK_SIZE_MB;

    @Value("${minio.serviceAddress}")
    private String SERVICE_ADDRESS;

    @Value("${minio.bucket.uploadFileSize}")
    private String UPLOAD_FILE_SIZE;

    @Value("${minio.bucket.uploadFileChunkSize}")
    private String UPLOAD_FILE_CHUNK_SIZE;

    private int CHUNK_SIZE;

    // 支持的视频扩展名
    private static final List<String> VIDEO_EXTENSIONS = Arrays.asList(
            "mp4", "avi", "mov", "mkv", "wmv", "flv", "mpeg", "mpg", "ts", "webm", "3gp", "3g2"
    );

    /**
     * 初始化文件信息，包括MD5、扩展名、内容类型和存储桶
     *
     * @param file 上传的MultipartFile文件
     * @return MultPartFileInfoInit 包含文件基础信息的对象
     */
    public MultPartFileInfoInit multipartFileInfoInit(MultipartFile file) {
        MultPartFileInfoInit fileInfoInit = new MultPartFileInfoInit();
        try (InputStream is = file.getInputStream()) {
            // 一次性计算MD5并获取扩展名
            String md5 = DigestUtils.md5DigestAsHex(is);
            String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());

            fileInfoInit.setFileName(file.getOriginalFilename());
            fileInfoInit.setFileMD5(md5);
            fileInfoInit.setExtension(ext != null ? ext.toLowerCase() : "");
            fileInfoInit.setFileType(determineContentType(ext));
            fileInfoInit.setBucketName(shouldUseVideoBucket(ext) ? BUCKET_VIDEOS : BUCKET_FILES);
//            fileInfoInit.setChunkSize(CHUNK_SIZE);
            fileInfoInit.setChunkSize(getChunkSize(file.getSize()));
            fileInfoInit.setFileSize(file.getSize());
        }catch(Exception e){
            throw new XueChengPlusException("文件初始化失败");
        }
        return fileInfoInit;
    }

    /**
     * 初始化文件信息，包括MD5、扩展名、内容类型和存储桶
     *
     * @param file 上传的File文件
     * @return MultPartFileInfoInit 包含文件基础信息的对象
     */
    public MultPartFileInfoInit multipartFileInfoInit(File file) {
        MultPartFileInfoInit fileInfoInit = new MultPartFileInfoInit();
        try (InputStream is = new FileInputStream(file)) {
            // 一次性计算MD5
            String md5 = DigestUtils.md5DigestAsHex(is);

            // 从文件名获取扩展名
            String fileName = file.getName();
            String ext = StringUtils.getFilenameExtension(fileName);

            fileInfoInit.setFileName(fileName);  // 改为获取文件自身名称
            fileInfoInit.setFileMD5(md5);
            fileInfoInit.setExtension(ext != null ? ext.toLowerCase() : "");
            fileInfoInit.setFileType(determineContentType(ext));
            fileInfoInit.setBucketName(shouldUseVideoBucket(ext) ? BUCKET_VIDEOS : BUCKET_FILES);

            // 文件大小改为通过File对象获取
            long fileSize = file.length();
            fileInfoInit.setChunkSize(getChunkSize(fileSize));  // 注意这里参数改为fileSize
            fileInfoInit.setFileSize(fileSize);  // 设置文件大小
        } catch(Exception e) {
            throw new XueChengPlusException("文件初始化失败: " + e.getMessage()); // 增强错误信息
        }
        return fileInfoInit;
    }

    /**
     * 上传图片到MinIO存储
     *
     * @param file 图片
     * @throws IOException 文件流操作异常时抛出
     * @throws MinioException MinIO客户端操作异常时抛出
     */
    private MultPartFileInfoInit uploadImages(MultipartFile file) throws IOException, MinioException {
        MultPartFileInfoInit fileInfo = multipartFileInfoInit(file);
        String objectPath = generateFinalFilePath(fileInfo.getFileMD5(),fileInfo.getExtension());

        try (InputStream inputStream = file.getInputStream()) {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(fileInfo.getBucketName() )
                            .object(objectPath)
                            .stream(inputStream, file.getSize(), -1)
                            .contentType(fileInfo.getFileType())
                            .build()
            );

            // 4. 返回文件在MinIO中的存储路径（格式: bucket/objectPath）
            return fileInfo;
        } catch (Exception e) {
            throw new MinioException("上传图片到MinIO失败: " + e);
        }
    }

    //从mInio中下载文件
    @Override
    public String downloadToTemp(String objectPath) throws IOException {
        MinioClient minioClient = minioConfig.minioClient();

        // 从路径中提取原始文件名
        String originalFileName = getFileNameFromPath(objectPath);

        // 创建临时文件（自动使用系统临时目录）
        Path tempFile = Files.createTempFile("minio_", "_" + originalFileName);
        String tempFilePath = tempFile.toAbsolutePath().toString();

        log.info("下载文件到临时路径: {}", tempFilePath);
        String[] bucketAndObject = parseMediaFileUrl(objectPath);

        try (InputStream stream = minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketAndObject[0])
                        .object(bucketAndObject[1])
                        .build()
        )) {
            // 使用Files.copy实现高效的文件写入
            Files.copy(stream, tempFile, StandardCopyOption.REPLACE_EXISTING);
        } catch (Exception e) {
            // 下载失败时删除临时文件
            Files.deleteIfExists(tempFile);
            log.error("MinIO文件下载失败 | 路径: {} | 错误: {}", objectPath, e.getMessage());
            throw new IOException("文件下载失败: " + e.getMessage(), e);
        }

        return tempFilePath;
    }

    //    通过数据库文件url 解析出 bucket 和 文件路径
    public String[] parseMediaFileUrl(String bucketAndObject){
        int firstSlash = bucketAndObject.indexOf('/');
        if (firstSlash <= 0) {
            System.err.println("URL格式无效: " + bucketAndObject);
            return null;
        }

        // 分离存储桶名和对象名
        String bucketName = bucketAndObject.substring(0, firstSlash);
        String objectName = bucketAndObject.substring(firstSlash + 1);

        return new String[]{bucketName, objectName};
    }


    /**
     * 从完整路径中提取文件名（兼容Windows/Linux路径分隔符）
     */
    private String getFileNameFromPath(String path) {
        // 处理空路径
        if (path == null || path.trim().isEmpty()) {
            return "unnamed_file";
        }

        // 标准化路径并提取文件名
        String normalizedPath = path.replace('\\', '/');
        int lastIndex = normalizedPath.lastIndexOf('/');

        // 返回文件名部分（如果没有分隔符则返回整个路径）
        return (lastIndex >= 0) ? normalizedPath.substring(lastIndex + 1) : normalizedPath;
    }

    /**
     * 根据文件扩展名确定内容类型
     *
     * @param extension 文件扩展名（不含点号）
     * @return String MIME内容类型字符串：
     *         视频文件 -> "video/bytes"
     *         其他文件 -> "application/octet-stream"
     *         空/无效扩展名 -> "application/octet-stream"
     */
    private String determineContentType(String extension) {

        if (extension == null || extension.trim().isEmpty()) {
            return "application/octet-stream";
        }
        String cleanExt = extension.trim().toLowerCase();        // 规范化扩展名（去掉首尾空格）
        // 核心检测：是否在视频扩展名列表中？
        return VIDEO_EXTENSIONS.contains(cleanExt) ? "video/bytes" : "application/octet-stream";
    }


    /**
     * 生成合并后的最终文件路径
     *
     * @param md5 文件MD5值
     * @param extension 文件扩展名
     * @return String 最终文件存储路径
     */
    private String generateFinalFilePath(String md5, String extension) {
        return String.format("%s/%s/%s.%s",
                md5.substring(0, 1),
                md5.substring(1, 3),
                md5,
                (extension == null || extension.isEmpty()) ? "bin" : extension
        );
    }

    /**
     * 根据文件扩展名确定是否使用视频存储桶
     *
     * @param extension 文件扩展名（不含点号）
     * @return boolean 是否使用视频存储桶（VIDEO_EXTENSIONS列表中的文件返回true）
     */
    private boolean shouldUseVideoBucket(String extension) {
        return extension != null && VIDEO_EXTENSIONS.contains(extension.toLowerCase());
    }

    // 根据文件大小获取分块大小 (单位：字节)
    public int getChunkSize(long fileSizeBytes) {
        long fileSizeMB = fileSizeBytes / (1024 * 1024);
        Map<Long, Integer> sizeMap = parseSizeConfig();

        // 文件小于5MB，直接使用文件大小
        if (fileSizeMB < 5) {
            return (int) fileSizeBytes;
        }

        // 查找匹配的分块大小配置
        for (Map.Entry<Long, Integer> entry : sizeMap.entrySet()) {
            if (fileSizeMB <= entry.getKey()) {
                return entry.getValue() * 1024 * 1024;
            }
        }

        // 未找到匹配，返回最大的分块大小
        return sizeMap.values().stream()
                .max(Comparator.naturalOrder())
                .orElse(10) * 1024 * 1024;
    }

    // 解析配置文件格式: "100-500-1000-10000" 和 "5-10-20-40"
    private Map<Long, Integer> parseSizeConfig() {
        String[] sizes = UPLOAD_FILE_SIZE.split("-");
        String[] chunks = UPLOAD_FILE_CHUNK_SIZE.split("-");

        Map<Long, Integer> map = new LinkedHashMap<>();
        for (int i = 0; i < sizes.length; i++) {
            long size = Long.parseLong(sizes[i]);
            int chunk = Integer.parseInt(chunks[i]);
            map.put(size, chunk);
        }
        return map;
    }

    /**
     * 将文件分割为多个块并上传到MinIO
     *
     * @param file 上传的MultipartFile文件
     * @return MultPartFileInfoInit 包含所有分块路径的文件信息对象
     * @throws IOException 文件读取异常或输入流错误时抛出
     */
    @Override
    public MultPartFileInfoInit splitFileToChunks(File file) throws IOException {
        MultPartFileInfoInit fileInfo = multipartFileInfoInit(file);
        fileInfo.setChunkPaths(new ArrayList<>()); // 初始化列表

        try (InputStream fileStream = new FileInputStream(file)) {
            byte[] buffer = new byte[Math.toIntExact(fileInfo.getChunkSize())];
            int bytesRead;
            int chunkIndex = 0;

            while ((bytesRead = fileStream.read(buffer)) != -1) {
                byte[] chunkData = Arrays.copyOf(buffer, bytesRead);
                String chunkPath = uploadChunk(fileInfo, chunkIndex, chunkData);
                fileInfo.getChunkPaths().add(chunkPath);
                chunkIndex++;
            }
        } catch (MinioException e) {
            throw new RuntimeException(e);
        }
        return fileInfo;
    }

    /**
     * 合并MinIO中的分块文件
     *
     * @param fileInfo 包含文件信息和分块路径的对象
    //     * @return String 合并后的文件完整路径
     */
    @Override
    public void mergeChunks(MultPartFileInfoInit fileInfo) throws ServerException, InsufficientDataException, ErrorResponseException, IOException, NoSuchAlgorithmException, InvalidKeyException, InvalidResponseException, XmlParserException, InternalException {
        String finalObjectName = generateFinalFilePath(fileInfo.getFileMD5(),fileInfo.getExtension());
        System.out.println("finalObjectName"+finalObjectName);
        // 2. 准备分块源对象列表
        List<ComposeSource> sources = new ArrayList<>();
        for (String chunkPath : fileInfo.getChunkPaths()) {
            //    分块路径格式为 "bucket/object/path"    提取对象路径（去掉桶名前缀）
            String objectPath = chunkPath.substring(fileInfo.getBucketName().length() + 1);
            sources.add(ComposeSource.builder().bucket(fileInfo.getBucketName()).object(objectPath).build());
        }
        // 3. 执行合并操作
        minioClient.composeObject(ComposeObjectArgs.builder().bucket(fileInfo.getBucketName()).object(finalObjectName).sources(sources).build());
        // 4. 清理临时分块
        cleanupChunks(fileInfo);

//        return fileInfo.getBucketName() + "/" + finalObjectName;
    }


    /**
     * 清理已合并的分块文件
     *
     * @param fileInfo 包含分块路径的文件信息
     */
    private void cleanupChunks(MultPartFileInfoInit fileInfo) {
        for (String chunkPath : fileInfo.getChunkPaths()) {
            // 提取对象路径（去掉桶名前缀）
            String objectPath = chunkPath.substring(fileInfo.getBucketName().length() + 1);

            try {
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(fileInfo.getBucketName())
                                .object(objectPath)
                                .build()
                );
            } catch (Exception e) {
                log.error("清理分块失败: {}", objectPath, e);
            }
        }
        log.info("已清理 {} 个分块文件", fileInfo.getChunkPaths().size());
    }

    /**
     * 上传单个文件块到MinIO存储 分块
     *
     * @param fileInfo 包含存储桶和内容类型信息的对象
     * @param chunkIndex 当前分块索引（从0开始）
     * @param chunkData 文件块二进制数据
     * @return String 上传成功的完整路径（格式：bucketName/objectPath）
     * @throws IOException 文件流操作异常时抛出
     * @throws MinioException MinIO客户端操作异常时抛出
     */
    private String uploadChunk(MultPartFileInfoInit fileInfo, int chunkIndex, byte[] chunkData)
            throws IOException, MinioException {

        String objectPath = generateChunkPath(fileInfo.getFileMD5(), chunkIndex);

        try (ByteArrayInputStream chunkStream = new ByteArrayInputStream(chunkData)) {
            minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(fileInfo.getBucketName())
                            .object(objectPath)
                            .stream(chunkStream, chunkData.length, -1)
                            .contentType(fileInfo.getFileType())
                            .build()
            );
            return fileInfo.getBucketName() + "/" + objectPath;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 生成MinIO分块存储路径（基于文件MD5和分块索引）
     *
     * @param fileMd5 文件的MD5哈希值
     * @param chunkIndex 当前分块索引（从0开始）
     * @return String 分块存储路径（格式：<一级目录>/<二级目录>/<文件MD5>/chunk_<索引>）
     *         路径示例: "0/4d/04d6dcf6.../chunk_0"
     */
    public static String generateChunkPath(String fileMd5, int chunkIndex) {
        return String.format("%s/%s/%s/chunk_%d",fileMd5.substring(0, 1),fileMd5.substring(1, 3),fileMd5,chunkIndex);
    }

    /**
     * 将文件分割为多个块并上传到MinIO
     *
     * @param file 上传的MultipartFile文件
     * @return MultPartFileInfoInit 包含所有分块路径的文件信息对象
     * @throws IOException 文件读取异常或输入流错误时抛出
     */
    @Override
    public MultPartFileInfoInit splitFileToChunks(MultipartFile file) throws IOException {
        MultPartFileInfoInit fileInfo = multipartFileInfoInit(file);
        fileInfo.setChunkPaths(new ArrayList<>()); // 初始化列表

        try (InputStream fileStream = file.getInputStream()) {
            byte[] buffer = new byte[Math.toIntExact(fileInfo.getChunkSize())];
            int bytesRead;
            int chunkIndex = 0;

            while ((bytesRead = fileStream.read(buffer)) != -1) {
                byte[] chunkData = Arrays.copyOf(buffer, bytesRead);
                String chunkPath = uploadChunk(fileInfo, chunkIndex, chunkData);
                fileInfo.getChunkPaths().add(chunkPath);
                chunkIndex++;
            }
        } catch (MinioException e) {
            throw new RuntimeException(e);
        }
        return fileInfo;
    }


}
